#include <cfloat>

#include <QFile>
#include <QFileDialog>
#include <QProcess>

#include "fuzzycontroller.h"
#include "datacontroller.h"

FuzzyController::FuzzyController(SystemControl* system):
        m_system(system)
{
}

double FuzzyController::getCrispOutput(const double &inputA,
                                       const double &inputB) const
{
    /* Get data controller instance. */
    DataController* system = DataController::instance();

    /* Get const references to the sets that are going to be worked with: */
    const Collection& inputOne = system->getInputOne();
    const Collection& inputTwo = system->getInputTwo();
    const Collection& output = system->getOutput();
    const QVector<Rule *> ruleList = system->getRuleList();

    /* Input the two crisp inputs to the input collections. */
    system->setInputOneFuzzyValues(inputA);
    system->setInputTwoFuzzyValues(inputB);

    /* Clear the data that previously existed in the output set. */
    system->setOutputFuzzyValues(-DBL_MAX);

    /* Determine which sets from the collections have a probability of
     * membership greater than 0.
     */
    QScopedPointer<QVector<BasicMembershipFunction *> >
            aMembers(inputOne.findTrueMembership());
    QScopedPointer<QVector<BasicMembershipFunction *> >
            bMembers(inputTwo.findTrueMembership());

    /* Apply the rules from the rule list to determine which output sets
     * will have a membership greater than 0.
     */
    for( int i = 0; i < ruleList.size(); ++i )
    {
        const QString& firstName = ruleList.at(i)->getFirstInput();
        const QString& secondName = ruleList.at(i)->getSecondInput();

        if( containsSet(*aMembers, firstName) &&
            containsSet(*bMembers, secondName))
        {
            const QString& outputName = ruleList.at(i)->getOutput();

            BasicMembershipFunction *aSet = inputOne.findSet(firstName);
            BasicMembershipFunction *bSet = inputTwo.findSet(secondName);
            BasicMembershipFunction *out = output.findSet(outputName);

            /* If any of these have come out to be null, there is no way that
             * the rules list and sets are in harmony, so major error
             */
            assert(aSet && bSet && out);

            double abTruth = conjunction(aSet->getFuzzyValue(),
                                         bSet->getFuzzyValue());

            out->setFuzzyValue(disjunction(out->getFuzzyValue(),
                                           abTruth));
        }
    }

    /* Get the centre of mass of the resulting composite output function
     * to get a crisp output value.
     */
    return system->getOutputCrispNumber();
}

QVector3D** FuzzyController::get3Doutput() const
{
    const int iterations = m_system->getOctaveResolution();

    DataController* data = DataController::instance();
    const Collection& inputOne = data->getInputOne();
    const Collection& inputTwo = data->getInputTwo();

    const double tempFirstCrisp = inputOne.getInputValue();
    const double tempSecondCrisp = inputTwo.getInputValue();

    double aStart = inputOne.getMinTerm();
    double bStart = inputTwo.getMinTerm();

    double aRange = inputOne.getMaxTerm() - aStart;
    double bRange = inputTwo.getMaxTerm() - bStart;

    double aPerInc = aRange / (iterations - 1);
    double bPerInc = bRange / (iterations - 1);

    QVector3D **outputTable = new QVector3D*[iterations];

    for(int i = 0; i < iterations; ++i) {
        outputTable[i] = new QVector3D[iterations];
        for(int j = 0; j < iterations; ++j) {
            outputTable[i][j] = QVector3D(aStart + i*aPerInc,
                                          bStart + j*bPerInc,
                                          getCrispOutput(aStart + i*aPerInc,
                                                         bStart + j*bPerInc));
        }
    }

    makeOctaveFile(outputTable, iterations);

    data->setInputOneFuzzyValues(tempFirstCrisp);
    data->setInputTwoFuzzyValues(tempSecondCrisp);

    return outputTable;
}

void FuzzyController::makeOctaveFile(QVector3D **data,
                                     const int& iterations) const
{
    /* File dialog */
    QString fileName = QFileDialog::getSaveFileName(0,
                                                    "Saving Octave Output",
                                                    "",
                                                    "m File (*.m)");

    /* Safety check */
    if ( fileName == "" )
        return;

    QFile writer(fileName);

    writer.open(QIODevice::WriteOnly | QIODevice::Text);

    /* The code here is kind of preliminary, since m_resolution could have
     * been changed between calls to the get3Doutput function and this one, so
     * some defensive measures may need to be taken to get that fixed.
     */
    writer.write("x = [\n");
    for(int i = 0; i < iterations; ++i) {
        for(int j = 0; j < iterations; ++j) {
            writer.write(QString::number(data[i][j].x()).toAscii());
            writer.write(" ");
        }
        writer.write("\n");
    }
    writer.write("];");
    writer.write("\n");

    writer.write("y = [ \n");
    for(int i = 0; i < iterations; ++i) {
        for(int j = 0; j < iterations; ++j) {
            writer.write(QString::number(data[i][j].y()).toAscii());
            writer.write(" ");
        }
        writer.write("\n");
    }
    writer.write("];");
    writer.write("\n");

    writer.write("z = [ \n");
    for(int i = 0; i < iterations; ++i) {
        for(int j = 0; j < iterations; ++j) {
            writer.write(QString::number(data[i][j].z()).toAscii());
            writer.write(" ");
        }
        writer.write("\n");
    }
    writer.write("];");
    writer.write("\n");

    /* color maps are: winter spring summer autumn rainbow prism pink ocean jet
       hsv gray copper cool bone
     */

    writer.write("colormap(winter);");
	writer.write("mesh(x,y,z);");
	writer.write("pause");
	writer.close();

    QProcess *process = new QProcess();
    /* My dad wants this to start detached, so that many can be open at the
     * same time.
     */
    process->startDetached(m_system->getOctavePath(), QStringList() << fileName);

//    const QString octave="octave";
//    process->startDetached(octave, QStringList() << fileName);

}

bool FuzzyController::containsSet(const QVector<BasicMembershipFunction *> &list,
                                  const QString &name) const
{
    for( int i = 0; i < list.size(); ++i )
		if(list[i]->getName().contains(name))/*contains causes problems?*/
            return true;
    return false;
}

GraphicalMF* FuzzyController::getTriangleMF(GraphicalMF* from) const {

    int oldSize = from->getSize();
    const int newSize = 3;

    if( newSize == oldSize ) return from;

    double points[newSize];
    QPointF* fromPoints = from->getPoints();

    if(oldSize == 4) {
        points[0] = fromPoints[0].x();
        points[1] = fromPoints[(oldSize - 1) / 2].x();
        points[2] = fromPoints[oldSize - 1].x();
    }
    else if (oldSize == 2) {
        points[0] = fromPoints[0].x();
        points[1] = (points[0] + fromPoints[1].x()) / 2;
        points[2] = fromPoints[1].x();
    }

    return new TriangleMF(points);

}

GraphicalMF* FuzzyController::getTrapezoidMF(GraphicalMF* from) const {

    int oldSize = from->getSize();
    const int newSize = 4;

    if( newSize == oldSize ) return from;

    double points[newSize];
    QPointF* fromPoints = from->getPoints();

    if(oldSize == 3) {
        points[0] = fromPoints[0].x();
        points[1] = (points[0] + fromPoints[1].x()) * 2/3;
        points[2] = (fromPoints[1].x() + fromPoints[2].x()) * 2/3;
        points[3] = fromPoints[2].x();
    }
    else if(oldSize == 2) {
        points[0] = fromPoints[0].x();
        points[1] = (points[0] + fromPoints[1].x()) * 1/3;
        points[2] = (points[0] + fromPoints[1].x()) * 2/3;
        points[3] = fromPoints[1].x();
    }

    return new TrapezoidMF(points);

}
